import socket
import struct
import json
import os
import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

# 使用原始字符串避免转义问题
shared_dir = r"D:\MyCode\python_project\Python-Network-Programming\Best Practice\文件传输\server\share"

# 确保共享目录存在
os.makedirs(shared_dir, exist_ok=True)
logging.info(f"共享目录: {shared_dir}")

host = ''
port = 8080

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(5)

logging.info(f'服务器监听端口 {port}')

def send_error(conn, error_msg):
    """发送错误消息给客户端"""
    header = {
        'error': error_msg,
        'file_size': 0
    }
    header_json = json.dumps(header).encode('utf-8')
    header_size = len(header_json)
    try:
        conn.sendall(struct.pack('>I', header_size))
        conn.sendall(header_json)
    except Exception as e:
        logging.error(f"发送错误失败: {str(e)}")

def send_file(conn, file_path):
    """发送文件给客户端"""
    try:
        file_size = os.path.getsize(file_path)
        
        # 构建报头
        header = {
            'filename': os.path.basename(file_path),
            'file_size': file_size,
            'md5': '待计算'  # 实际应用应计算MD5
        }
        header_json = json.dumps(header).encode('utf-8')
        header_size = len(header_json)
        
        # 发送报头
        conn.sendall(struct.pack('>I', header_size))
        conn.sendall(header_json)
        
        # 高效发送文件
        sent_bytes = 0
        with open(file_path, 'rb') as f:
            while sent_bytes < file_size:
                chunk = f.read(4096)  # 4KB块
                conn.sendall(chunk)
                sent_bytes += len(chunk)
                logging.debug(f"已发送 {sent_bytes}/{file_size} 字节")
        
        logging.info(f"文件 {file_path} 发送完成")
        return True
        
    except Exception as e:
        logging.error(f"发送文件失败: {str(e)}")
        send_error(conn, f"服务器错误: {str(e)}")
        return False

counter = 0
while True:
    try:
        conn, addr = s.accept()
        counter += 1
        client_id = f"客户端#{counter}"
        logging.info(f"{client_id} 已连接: {addr}")
        
        while True:
            # 接收命令
            try:
                data = conn.recv(1024)
                if not data:
                    logging.info(f"{client_id} 断开连接")
                    break
                    
                cmd = data.decode('utf-8').strip()
                logging.info(f"{client_id} 命令: {cmd}")
                
                # 解析命令
                parts = cmd.split()
                if len(parts) < 2 or parts[0].lower() != 'get':
                    send_error(conn, "无效命令格式。用法: get filename")
                    continue
                    
                filename = parts[1]
                file_path = os.path.join(shared_dir, filename)
                
                # 检查文件是否存在
                if not os.path.exists(file_path):
                    send_error(conn, f"文件不存在: {filename}")
                    continue
                    
                if not os.path.isfile(file_path):
                    send_error(conn, f"不是文件: {filename}")
                    continue
                    
                # 发送文件
                send_file(conn, file_path)
                
            except UnicodeDecodeError:
                send_error(conn, "命令编码错误")
            except Exception as e:
                logging.error(f"{client_id} 处理错误: {str(e)}")
                break
                
    except (ConnectionResetError, BrokenPipeError):
        logging.warning(f"{client_id} 连接意外断开")
    except Exception as e:
        logging.error(f"严重错误: {str(e)}")
    finally:
        try:
            conn.close()
        except:
            pass
        logging.info(f"{client_id} 连接关闭")

s.close()